
import { math } from 'cc';
import { ecsclass, EcsComponent, EcsEntity } from 'db://pkg/@gamex/cc-ecs';
import { INodeReadonly } from './NodeComponent';
import { IRenderReadonly } from './RenderComponent';

// 将Base(类)对应实例中所有值为Type类型的key去除，返回string的复合类型(被过滤后的key)
type KeyofFilterType<Base, ExcludedType> = {
    [key in keyof Base]: Base[key] extends ExcludedType ? never : key;
}[keyof Base];

// 将Base(类)对应实例中所有键为Key类型的key去除，返回string的复合类型(被过滤后的key)
type KeyofFilterKey<Base, ExcludedKey> = {
    [key in keyof Base]: key extends ExcludedKey ? never : key;
}[keyof Base];

type ExcludedKey = keyof EcsComponent | keyof INodeReadonly | keyof IRenderReadonly;
type PropType<Base> = Partial<
    Pick<Base, KeyofFilterType<
        Pick<Base, KeyofFilterKey<Base, ExcludedKey>>,
        Function | string | object
    >>
>;

interface ITweenOption {
    /**
     * @en
     * Easing function, you can pass in a string or custom function.
     * @zh
     * 缓动函数，可以使用已有的，也可以传入自定义的函数。
     */
    easing?: ((k: number) => number);
}

type IObject = { [key: string]: any };

enum ActionType {
    None,
    To,
    By
}

// 动作基类
class Action {
}

// 属性动作
class PropAction extends Action {
    type: ActionType = ActionType.None;
    /**进度 */
    ratio: number = 0;
    /**时间 */
    time: number = 0;
    /**目标 */
    target: EcsComponent = null;
    /**结束属性 */
    end: { [key: string]: any } = null;
    /**开始属性 */
    start: { [key: string]: any } = null;
    /**缓动函数 */
    easing: ITweenOption['easing'] = null;
}

// 贝塞尔属性动作
class BezierPropAction extends PropAction {
    /**控制位属性 */
    control: { [key: string]: any } = null;
}

// 等待动作
class DelayAction extends Action {
    delay: number = 0;
}

// 回调动作
class CallAction extends Action {
    callback: Function = null;
}

@ecsclass('TweenComponent')
export class TweenComponent extends EcsComponent<EcsEntity> {
    static allowMultiple = true;
    static allowRecycling = true;
    protected onDisable() {
        this.actions.length = 0;
    }

    /**动作列表 */
    private actions: Action[] = [];

    delay(delay: number) {
        const action = new DelayAction();
        action.delay = delay;
        this.actions.push(action);
        return this;
    }

    call(callback: Function) {
        const action = new CallAction();
        action.callback = callback;
        this.actions.push(action);
        return this;
    }

    to<T extends EcsComponent>(time: number, target: T, props: PropType<T>, opts?: ITweenOption) {
        const action = new PropAction();
        action.type = ActionType.To;
        action.ratio = 0;
        action.target = target;
        action.time = Math.max(time, 0);
        action.end = TweenComponent.getCopyValue(props);
        action.start = null;// 运行时动态计算
        action.easing = opts?.easing || null;
        this.actions.push(action);
        return this;
    }

    by<T extends EcsComponent>(time: number, target: T, props: PropType<T>, opts?: ITweenOption) {
        const action = new PropAction();
        action.type = ActionType.By;
        action.ratio = 0;
        action.target = target;
        action.time = Math.max(time, 0);
        action.end = TweenComponent.getCopyValue(props);// 运行时动态计算
        action.start = null;// 运行时动态计算
        action.easing = opts?.easing || null;
        this.actions.push(action);
        return this;
    }

    bezierTo<T extends EcsComponent, K extends PropType<T>>(time: number, target: T, props: K, control: Required<K>, opts?: ITweenOption) {
        const action = new BezierPropAction();
        action.type = ActionType.To;
        action.ratio = 0;
        action.target = target;
        action.time = Math.max(time, 0);
        action.control = TweenComponent.getStartValue(props, control);
        action.end = TweenComponent.getCopyValue(props);
        action.start = null;// 运行时动态计算
        action.easing = opts?.easing || null;
        this.actions.push(action);
        return this;
    }

    bezierBy<T extends EcsComponent, K extends PropType<T>>(time: number, target: T, props: K, control: Required<K>, opts?: ITweenOption) {
        const action = new BezierPropAction();
        action.type = ActionType.By;
        action.ratio = 0;
        action.target = target;
        action.time = Math.max(time, 0);
        action.end = TweenComponent.getCopyValue(props);// 运行时动态计算
        action.control = TweenComponent.getCopyValue(control);// 运行时动态计算
        action.start = null;// 运行时动态计算
        action.easing = opts?.easing || null;
        this.actions.push(action);
        this.actions.push(action);
    }

    /**
     * 拷贝数值
     */
    private static getCopyValue(props: IObject) {
        const copy: IObject = {};

        for (const key in props) {
            if (Object.prototype.hasOwnProperty.call(props, key)) {
                if (typeof props[key] === 'number' || typeof props[key] === 'boolean') {
                    copy[key] = props[key];
                } else if (props[key] && typeof props[key] === 'object') {
                    copy[key] = this.getCopyValue(props[key]);
                }
            }
        }
        return copy;
    }

    /**
     * 获取目标值
     * @param props 差值
     * @param target 原始值
     */
    private static getEndValue(props: IObject, target: IObject = {}) {
        const end: IObject = {};

        for (const key in props) {
            if (Object.prototype.hasOwnProperty.call(props, key)) {
                if (typeof props[key] === 'number') {
                    end[key] = props[key] + (target[key] || 0);
                } else if (typeof props[key] === 'boolean') {
                    end[key] = props[key];
                } else if (props[key] && typeof props[key] === 'object') {
                    end[key] = this.getEndValue(props[key], target[key]);
                }
            }
        }
        return end;
    }

    /**
     * 获取初始值
     * @param props 作为过滤使用
     * @param target 原始值
     */
    private static getStartValue(props: IObject, target: IObject = {}) {
        const start: IObject = {};

        for (const key in props) {
            if (Object.prototype.hasOwnProperty.call(props, key)) {
                if (typeof props[key] === 'number') {
                    start[key] = target[key] || 0;
                } else if (typeof props[key] === 'boolean') {
                    start[key] = target[key] || false;
                } else if (props[key] && typeof props[key] === 'object') {
                    start[key] = this.getStartValue(props[key], target[key]);
                }
            }
        }
        return start;
    }

    private static lerp(ratio: number, start: IObject, end: IObject, data: IObject = {}) {
        for (const key in start) {
            if (Object.prototype.hasOwnProperty.call(start, key)) {
                if (typeof start[key] === 'number') {
                    data[key] = math.lerp(start[key], end[key], ratio);
                } else if (typeof start[key] === 'boolean') {
                    data[key] = ratio >= 1 ? end[key] : start[key];
                } else if (start[key] && typeof start[key] === 'object') {
                    this.lerp(ratio, start[key], end[key], data[key]);
                }
            }
        }
    }

    private static bezier(ratio: number, start: IObject, control: IObject, end: IObject, data: IObject = {}) {
        for (const key in start) {
            if (Object.prototype.hasOwnProperty.call(start, key)) {
                if (typeof start[key] === 'number') {
                    const n1 = math.lerp(start[key], control[key], ratio);
                    const n2 = math.lerp(control[key], end[key], ratio);
                    data[key] = math.lerp(n1, n2, ratio);
                } else if (typeof start[key] === 'boolean') {
                    data[key] = ratio >= 1 ? end[key] : start[key];
                } else if (start[key] && typeof start[key] === 'object') {
                    this.lerp(ratio, start[key], end[key], data[key]);
                }
            }
        }
    }

    /**************************以下为系统调用**************************/
    static systemExecute(tween: TweenComponent, dt: number) {
        const action = tween.actions[0];
        if (action instanceof DelayAction) {
            action.delay -= dt;
            if (action.delay <= 0) {
                tween.actions.shift();
            }
        } else if (action instanceof CallAction) {
            if (action.callback) {
                tween.actions.shift();
                action.callback();
            }
        } else if (action instanceof PropAction) {
            if (action.target.isValid) {
                // 整体进度
                action.ratio = Math.min(action.ratio + dt / action.time, 1);
                // 完成移除
                if (action.ratio >= 1) {
                    tween.actions.shift();
                }
                // 应用缓动函数后的进度
                const ratio = action.easing ? action.easing(action.ratio) : action.ratio;
                // 计算动态数值
                if (!action.start) {
                    // 初始化起点坐标
                    action.start = TweenComponent.getStartValue(action.end, action.target);
                    if (action.type === ActionType.By) {
                        if (action instanceof BezierPropAction) {
                            action.end = TweenComponent.getEndValue(action.end, action.target);
                            action.control = TweenComponent.getStartValue(action.control, action.target);
                        } else {
                            action.end = TweenComponent.getEndValue(action.end, action.target);
                        }
                    }
                }
                // 插值运算
                if (action instanceof BezierPropAction) {
                    TweenComponent.bezier(ratio, action.start, action.control, action.end, action.target);
                } else {
                    TweenComponent.lerp(ratio, action.start, action.end, action.target);
                }
            } else {
                tween.actions.shift();
            }
        } else {
            tween.actions.shift();
        }

        return tween.actions.length;
    }
}